31. Procesamiento de XML

XML es un lenguaje de marcado para escribir documentos estructurados. Se usa para multitud de aplicaciones y por tanto deben conocerse al menos los rudimentos.

Un documento XML está jerárquicamente dividido por elementos identificados por etiquetas (tags). La etiqueta que marca el comienzo se identifica con una palabra entre corchetes angulares, mientras que la etiqueta que marca el final se identifica con un carácter / adicional antes de la misma palabra. Por ejemplo:

<root>
...
</root>

Dentro de un elemento puede haber otros elementos. Por ejemplo:

<root>
<origen>...</origen>
<nombre>...</nombre>
</root>

Cada etiqueta puede tener atributos que se incluyen justo después de la etiqueta de comienzo con los valores entre comillas:

<root id="28013" version="1.0">
...
</root>

Un elemento también puede contener texto entre las etiquetas de comienzo y fin:

<nombre>Aranjuez</nombre>

Un documento XML bien formado solo contiene un elemento raíz (no contenido en ningún otro elemento). Por tanto al leer un documento XML se genera un arbol de elementos.

La biblioteca estándar de Python incluye varios módulos relacionados con la lectura y escritura de documentos XML. Nosotros solo comentaremos el más sencillo. Consulta la documentación de Python si necesitas ampliar más.

31.1. Leer con ElementTree

El procesador de XML más simple y ligero de los incluidos en Python es xml.etree.ElementTree. Ilustraremos su uso con un archivo XML como los empleados en el trabajo en grupo.

import requests
r = requests.get('http://www.aemet.es/xml/municipios/localidad_28013.xml')
xml = r.text.encode('utf-8')

Ahora la variable xml contiene una cadena con todo el documento XML. Veamos cómo interpretarlo con ElementTree.

import xml.etree.ElementTree as ET
root = ET.fromstring(xml)

La función fromstring devuelve un objeto de tipo Element, el elemento raíz del documento XML. Element permite acceder a todos los componentes de un elemento con estas funciones:

  • e.iter('etiqueta') devuelve todos los elementos etiqueta que haya dentro del elemento e. Incluso los que estén dentro de otros elementos contenidos dentro de e.
  • e.find('etiqueta') devuelve el primer elemento etiqueta que es hijo directo de e.
  • e.findall('etiqueta') devuelve todos los elementos etiqueta que son hijos directos de e.
  • e.text devuelve la cadena correspondiente al texto del elemento e.
  • e.attrib devuelve los atributos del elemento e como un diccionario.
  • e.get('attr') devuelve el valor del atributo attr del elemento e.

Veamos un ejemplo que imprime la humedad mínima y máxima para los próximos días.

for dia in root.iter('dia'):
    print '{0}:'.format(dia.get('fecha')),
    humedad = dia.find('humedad_relativa')
    hmax = int(humedad.find('maxima').text)
    hmin = int(humedad.find('minima').text)
    print 'humedad {0}/{1}'.format(hmin, hmax)
2015-12-25: humedad 70/100
2015-12-26: humedad 55/100
2015-12-27: humedad 45/100
2015-12-28: humedad 60/95
2015-12-29: humedad 75/100
2015-12-30: humedad 65/95
2015-12-31: humedad 90/100

31.2. Leer con BeautifulSoup

Nota: En *BeautifulSoup* se usan atributos de Python para representar elementos XML. No confundas los *atributos* de Python con los *atributos* de un elemento XML. Los atributos de XML son parejas clave/valor que se pueden incluir en las etiquetas de comienzo de cada elemento.

BeautifulSoup es una biblioteca que simplifica notablemente la lectura y escritura de documentos XML. En BeautifulSoup la jerarquía del documento se traslada automáticamente a Python en forma de atributos de objeto. Así, por ejemplo, si el documento está contenido en un elemento root entonces lo devuelto por BeautifulSoup tendrá un atributo root.

from bs4 import BeautifulSoup
soup = BeautifulSoup(xml, 'lxml')

Así, soup tendrá un atributo root y a su vez ese atributo tendrá un atributo prediccion, etc. Tenemos varias formas de recorrer los elementos. Si usamos los paréntesis como si soup fuera una función podemos buscar todos los elementos con una etiqueta determinada. Por ejemplo, soup('dia') nos devolverá todos los elementos con etiqueta dia. En cambio si usamos los corchetes, como si se tratara de una lista, podemos acceder a los atributos del elemento. Por ejemplo si dia es un elemento con etiqueta 'dia' entonces dia['fecha'] es el valor del atributo fecha del elemento dia en el documento XML.

Si solo hay un elemento con esa etiqueta entonces podemos usar el atributo con el mismo nombre. Por ejemplo, los elementos con etiqueta dia tienen solo un elemento humedad_relativa. Por tanto podemos acceder a él usando el atributo del mismo nombre. Si hay múltiples elementos con la misma etiqueta el atributo solo sirve para acceder al primero.

Para obtener el texto de cada elemento podemos acceder al atributo string. Por ejemplo, soup.root.dia.prediccion.humedad_relativa.maxima.string es el texto del elemento maxima, dentro del elemento humedad_relativa, dentro del elemento prediccion, dentro del primer elemento dia, dentro del elemento root del documento.

Así, el código equivalente al ejemplo de ElementTree sería:

for dia in soup('dia'):
    humedad = dia.humedad_relativa
    hmax = int(humedad.maxima.string)
    hmin = int(humedad.minima.string)
    print '{0}: humedad {1}/{2}'.format(dia['fecha'], hmin, hmax)
2015-12-25: humedad 70/100
2015-12-26: humedad 55/100
2015-12-27: humedad 45/100
2015-12-28: humedad 60/95
2015-12-29: humedad 75/100
2015-12-30: humedad 65/95
2015-12-31: humedad 90/100
Next Section - 32. Prueba de progreso 1ºA